{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Mortgage Workflow\n",
    "\n",
    "The original implementation can be found at the [rapidsai notebooks-extendeds](https://github.com/rapidsai/notebooks-extended/blob/462b3b9/intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb) site. This notebook is a re-implementation using `gQuant`.\n",
    "\n",
    "## The Dataset\n",
    "The dataset used with this workflow is derived from [Fannie Mae’s Single-Family Loan Performance Data](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html) with all rights reserved by Fannie Mae. This processed dataset is redistributed with permission and consent from Fannie Mae.\n",
    "\n",
    "To acquire this dataset, please visit [RAPIDS Datasets Homepage](https://docs.rapids.ai/datasets/mortgage-data)\n",
    "\n",
    "## Introduction\n",
    "The Mortgage workflow is composed of three core phases:\n",
    "\n",
    "1. ETL - Extract, Transform, Load\n",
    "2. Data Conversion\n",
    "3. ML - Training\n",
    "\n",
    "### ETL\n",
    "Data is \n",
    "1. Read in from storage\n",
    "2. Transformed to emphasize key features\n",
    "3. Loaded into volatile memory for conversion\n",
    "\n",
    "### Data Conversion\n",
    "Features are\n",
    "1. Broken into (labels, data) pairs\n",
    "2. Distributed across dask workers if using Dask.\n",
    "3. Converted into compressed sparse row (CSR) matrix (DMatrix) format for XGBoost\n",
    "\n",
    "### Machine Learning\n",
    "The CSR data is fed into XGBoost or with a distributed training session with Dask-XGBoost"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1821"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# WARMUP CUDF ENGINE - OPTIONAL\n",
    "import gc  # python garbage collector\n",
    "import cudf\n",
    "\n",
    "# warmup\n",
    "s = cudf.Series([1,2,3,None,4], nan_as_null=False)\n",
    "\n",
    "del(s)\n",
    "gc.collect()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mortgage gQuant Workflow for ETL\n",
    "\n",
    "Two modules are provided with this notebook and should be in the same location (directory) as this notebook: `mortgage_common.py` and `mortgage_gquant_plugins.py`. The plugins module contains the individuals tasks for loading the mortgage data from csv (command separated) files into `cudf` dataframes and processing/transforming these dataframes for mortgage delinquency analysis. As an example the gQuant task to calculate loan delinquecy status period features is shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "class CreateEverFeatures(Node):\n",
      "    '''gQuant task/node to calculate delinquecy status period features.\n",
      "    Refer to columns_setup method for the columns produced.\n",
      "    '''\n",
      "    def columns_setup(self):\n",
      "        self.required = OrderedDict([\n",
      "            ('loan_id', 'int64'),\n",
      "            ('current_loan_delinquency_status', 'int32')\n",
      "        ])\n",
      "\n",
      "        self.retention = {\n",
      "            'loan_id': 'int64',\n",
      "            'ever_30': 'int8',\n",
      "            'ever_90': 'int8',\n",
      "            'ever_180': 'int8'\n",
      "        }\n",
      "\n",
      "    def process(self, inputs):\n",
      "        '''\n",
      "        '''\n",
      "        gdf = inputs[0]\n",
      "        everdf = gdf[['loan_id', 'current_loan_delinquency_status']]\n",
      "        everdf = everdf.groupby('loan_id', method='hash', as_index=False).max()\n",
      "        everdf['ever_30'] = \\\n",
      "            (everdf['current_loan_delinquency_status'] >= 1).astype('int8')\n",
      "        everdf['ever_90'] = \\\n",
      "            (everdf['current_loan_delinquency_status'] >= 3).astype('int8')\n",
      "        everdf['ever_180'] = \\\n",
      "            (everdf['current_loan_delinquency_status'] >= 6).astype('int8')\n",
      "        everdf.drop_column('current_loan_delinquency_status')\n",
      "\n",
      "        return everdf\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import inspect\n",
    "from mortgage_gquant_plugins import CreateEverFeatures\n",
    "\n",
    "print(inspect.getsource(CreateEverFeatures))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We create a worfklow by defining tasks and specifying their configuration (parameters) and inputs (for basics tutorial on gQuant refer to [01_tutorial.ipynb](https://github.com/rapidsai/gQuant/blob/master/notebooks/01_tutorial.ipynb) and custom plugins [05_customize_nodes.ipynb](https://github.com/rapidsai/gQuant/blob/master/notebooks/05_customize_nodes.ipynb)). The workflow to calculate the mortgage features and delinquecy is defined in the `mortgage_etl_workflow_def` function in `mortgage_common` module. Its code is shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "def mortgage_etl_workflow_def(\n",
      "        csvfile_names=None, csvfile_acqdata=None,\n",
      "        csvfile_perfdata=None):\n",
      "    '''Define the ETL (extract-transform-load) portion of the mortgage\n",
      "    workflow.\n",
      "\n",
      "    :returns: gQuant task-spec list. Currently a simple list of dictionaries.\n",
      "        Each dict is a task-spec per TaskSpecSchema.\n",
      "    :rtype: list\n",
      "    '''\n",
      "    from gquant.dataframe_flow import TaskSpecSchema\n",
      "\n",
      "    _basedir = os.path.dirname(__file__)\n",
      "\n",
      "    mortgage_lib_module = os.path.join(_basedir, 'mortgage_gquant_plugins.py')\n",
      "\n",
      "    # print('CSVFILE_ACQDATA: ', csvfile_acqdata)\n",
      "    # print('CSVFILE_PERFDATA: ', csvfile_perfdata)\n",
      "\n",
      "    # load acquisition\n",
      "    load_acqdata_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.load_acqdata_task_name,\n",
      "        TaskSpecSchema.node_type: 'CsvMortgageAcquisitionDataLoader',\n",
      "        TaskSpecSchema.conf: {\n",
      "            'csvfile_names': csvfile_names,\n",
      "            'csvfile_acqdata': csvfile_acqdata\n",
      "        },\n",
      "        TaskSpecSchema.inputs: [],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    # load performance data\n",
      "    load_perfdata_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.load_perfdata_task_name,\n",
      "        TaskSpecSchema.node_type: 'CsvMortgagePerformanceDataLoader',\n",
      "        TaskSpecSchema.conf: {\n",
      "            'csvfile_perfdata': csvfile_perfdata\n",
      "        },\n",
      "        TaskSpecSchema.inputs: [],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    # calculate loan delinquency stats\n",
      "    ever_feat_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.ever_feat_task_name,\n",
      "        TaskSpecSchema.node_type: 'CreateEverFeatures',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [MortgageTaskNames.load_perfdata_task_name],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    delinq_feat_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.delinq_feat_task_name,\n",
      "        TaskSpecSchema.node_type: 'CreateDelinqFeatures',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [MortgageTaskNames.load_perfdata_task_name],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    join_perf_ever_delinq_feat_task = {\n",
      "        TaskSpecSchema.task_id:\n",
      "            MortgageTaskNames.join_perf_ever_delinq_feat_task_name,\n",
      "        TaskSpecSchema.node_type: 'JoinPerfEverDelinqFeatures',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [\n",
      "            MortgageTaskNames.load_perfdata_task_name,\n",
      "            MortgageTaskNames.ever_feat_task_name,\n",
      "            MortgageTaskNames.delinq_feat_task_name\n",
      "        ],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    create_12mon_feat_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.create_12mon_feat_task_name,\n",
      "        TaskSpecSchema.node_type: 'Create12MonFeatures',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [\n",
      "            MortgageTaskNames.join_perf_ever_delinq_feat_task_name\n",
      "        ],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    final_perf_delinq_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.final_perf_delinq_task_name,\n",
      "        TaskSpecSchema.node_type: 'FinalPerfDelinq',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [\n",
      "            MortgageTaskNames.load_perfdata_task_name,\n",
      "            MortgageTaskNames.join_perf_ever_delinq_feat_task_name,\n",
      "            MortgageTaskNames.create_12mon_feat_task_name\n",
      "        ],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    final_perf_acq_task = {\n",
      "        TaskSpecSchema.task_id: MortgageTaskNames.final_perf_acq_task_name,\n",
      "        TaskSpecSchema.node_type: 'JoinFinalPerfAcqClean',\n",
      "        TaskSpecSchema.conf: dict(),\n",
      "        TaskSpecSchema.inputs: [\n",
      "            MortgageTaskNames.final_perf_delinq_task_name,\n",
      "            MortgageTaskNames.load_acqdata_task_name\n",
      "        ],\n",
      "        TaskSpecSchema.filepath: mortgage_lib_module\n",
      "    }\n",
      "\n",
      "    task_spec_list = [\n",
      "        load_acqdata_task, load_perfdata_task,\n",
      "        ever_feat_task, delinq_feat_task, join_perf_ever_delinq_feat_task,\n",
      "        create_12mon_feat_task, final_perf_delinq_task, final_perf_acq_task\n",
      "    ]\n",
      "\n",
      "    return task_spec_list\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import inspect\n",
    "from mortgage_common import mortgage_etl_workflow_def\n",
    "\n",
    "print(inspect.getsource(mortgage_etl_workflow_def))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's visualize the mortgage ETL workflow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from gquant.dataframe_flow import TaskGraph\n",
    "\n",
    "task_spec_list = mortgage_etl_workflow_def()\n",
    "task_graph = TaskGraph(task_spec_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's run the workflow and inspect the resultant `final_per_acq_df` dataframe. Adjust your paths below to wherever you downloaded the mortgage dataset to. The `mortgage_data` below is assumed to be in the same directory as this notebook (could be a symlink to wherever the actual data resides), otherwise adjust the paths."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q1.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q1.txt\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from gquant.dataframe_flow import TaskGraph\n",
    "from mortgage_common import (\n",
    "    mortgage_etl_workflow_def, MortgageTaskNames)\n",
    "\n",
    "# mortgage_data_path = '/datasets/rapids_data/mortgage'\n",
    "mortgage_data_path = './mortgage_data'\n",
    "# _basedir = os.path.abspath('')  # path of current notebook\n",
    "# mortgage_data_path = os.path.join(_basedir, 'mortgage_data')\n",
    "csvfile_names = os.path.join(mortgage_data_path, 'names.csv')\n",
    "acq_data_path = os.path.join(mortgage_data_path, 'acq')\n",
    "perf_data_path = os.path.join(mortgage_data_path, 'perf')\n",
    "\n",
    "# Some files out of the mortgage dataset.\n",
    "csvfile_acqdata = os.path.join(acq_data_path, 'Acquisition_2000Q1.txt')\n",
    "csvfile_perfdata = os.path.join(perf_data_path, 'Performance_2000Q1.txt_0')\n",
    "\n",
    "gquant_task_spec_list = mortgage_etl_workflow_def(\n",
    "    csvfile_names, csvfile_acqdata, csvfile_perfdata)\n",
    "out_list = [MortgageTaskNames.final_perf_acq_task_name]\n",
    "\n",
    "task_graph = TaskGraph(gquant_task_spec_list)\n",
    "\n",
    "(final_perf_acq_df,) = task_graph.run(out_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mortgage Workflow Ouput CUDF Dataframe:\n",
      "     servicer  interest_rate  current_actual_upb  loan_age  remaining_months_to_legal_maturity  adj_remaining_months_to_maturity  msa ...  relocation_mortgage_indicator\n",
      "0      -1.0            8.0             74319.0      12.0                               348.0                             347.0  0.0 ...                           -1.0\n",
      "1      -1.0            8.0            73635.48      24.0                               336.0                             335.0  0.0 ...                           -1.0\n",
      "2      -1.0            8.0            72795.41      36.0                               324.0                             322.0  0.0 ...                           -1.0\n",
      "3      -1.0            8.0                -1.0       1.0                               359.0                             358.0  0.0 ...                           -1.0\n",
      "4      -1.0            8.0            74264.14      13.0                               347.0                             346.0  0.0 ...                           -1.0\n",
      "5      -1.0            8.0            73576.06      25.0                               335.0                             334.0  0.0 ...                           -1.0\n",
      "6      -1.0            8.0            72680.39      37.0                               323.0                             320.0  0.0 ...                           -1.0\n",
      "7      -1.0            8.0                -1.0       2.0                               358.0                             357.0  0.0 ...                           -1.0\n",
      "8      -1.0            8.0            74208.91      14.0                               346.0                             345.0  0.0 ...                           -1.0\n",
      "9      -1.0            8.0            73516.25      26.0                               334.0                             333.0  0.0 ...                           -1.0\n",
      "[9094668 more rows]\n",
      "[38 more columns]\n"
     ]
    }
   ],
   "source": [
    "print('Mortgage Workflow Ouput CUDF Dataframe:\\n', final_perf_acq_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can indirectly see how much memory is being occupied on the GPU by this cudf dataframe. In my case I see `1863 MB` (assuming only this notebook is running and using the GPU)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pid, process_name, used_gpu_memory [MiB]\n",
      "8165, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 1863 MiB\n"
     ]
    }
   ],
   "source": [
    "!nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can clear this GPU memory by deleting the cudf dataframe and running python garbage collector to force garbage collection. After clearing I see `207 MB` occupied on the GPU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pid, process_name, used_gpu_memory [MiB]\n",
      "8165, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 207 MiB\n"
     ]
    }
   ],
   "source": [
    "import gc  # python garbage collector\n",
    "\n",
    "del(final_perf_acq_df)\n",
    "gc.collect()\n",
    "\n",
    "!nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mortgage Workflow Runner\n",
    "\n",
    "The example above loads just one performance and acqusition datafile. The mortgage dataset is broken down into many csv files. The complete worfklow reads these csv files into cudf dataframes, does all the processing on the GPU, then converts to PyArrow table (essentially arrow dataframes), concatenates the arrow tables, and as a final stage converts into one massive pandas dataframe. From this concatenated dataframe the delinquency column is used as labels and the remaining columns are used as training features. The xgboost DMatrix is instantiated from the features and labels and passed to the xgboost booster trainer. The xgboost trainer copies the data to GPU again and trains on GPU.\n",
    "\n",
    "Below we define the complete data training workflow. This is the non-distributed implementation. The dask distributed implementation will follow. The parameters for the mortgage runner are displayed (limited to 2 files). Below I load 12 files for the actual run. Adjust the `part_count` to something manageable on your system. The limitation will be the host RAM for how many dataframes can be concatenated and the DMatrix instantiated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parameters configuration for Mortgage Workflow Runner (shortened to 2 files)\n",
      "[\n",
      "  {\n",
      "    \"replace_spec\": {\n",
      "      \"acqdata\": {\n",
      "        \"conf\": {\n",
      "          \"csvfile_names\": \"./mortgage_data/names.csv\",\n",
      "          \"csvfile_acqdata\": \"./mortgage_data/acq/Acquisition_2000Q1.txt\"\n",
      "        }\n",
      "      },\n",
      "      \"perfdata\": {\n",
      "        \"conf\": {\n",
      "          \"csvfile_perfdata\": \"./mortgage_data/perf/Performance_2000Q1.txt_0\"\n",
      "        }\n",
      "      }\n",
      "    },\n",
      "    \"task_spec_list\": \"This is gquant_task_spec_list\",\n",
      "    \"out_list\": [\n",
      "      \"final_perf_acq_df\"\n",
      "    ]\n",
      "  },\n",
      "  {\n",
      "    \"replace_spec\": {\n",
      "      \"acqdata\": {\n",
      "        \"conf\": {\n",
      "          \"csvfile_names\": \"./mortgage_data/names.csv\",\n",
      "          \"csvfile_acqdata\": \"./mortgage_data/acq/Acquisition_2000Q2.txt\"\n",
      "        }\n",
      "      },\n",
      "      \"perfdata\": {\n",
      "        \"conf\": {\n",
      "          \"csvfile_perfdata\": \"./mortgage_data/perf/Performance_2000Q2.txt_0\"\n",
      "        }\n",
      "      }\n",
      "    },\n",
      "    \"task_spec_list\": \"This is gquant_task_spec_list\",\n",
      "    \"out_list\": [\n",
      "      \"final_perf_acq_df\"\n",
      "    ]\n",
      "  }\n",
      "]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAATEAAACbCAYAAAAKlaxUAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nO3deVgT974/8HfYIQRQwo4sObLI2oolWLWiQrU+7rYWRW2tora2ntpTj/Xca9tzaWs997S9p7e1da97677Wq4La1qpgEdkVlE0gEoIQIJCQ5fP7wx9zjAugBELw+3qeeSAzk5nPJJN3Zst3eEREYBiGMVFmxi6AYRimK1iIMQxj0liIMQxj0iyMXQDTO8jlcuh0Omg0GjQ2NgIAWlpaoFQquXHuf/wwdXV1Hc7L3t4elpaWjxxubm4OBweHhz62sbGBra0tAMDBwQHm5uYdzo/p21iImYC6ujrI5XKuUygUaGpqQlNTE1QqFeRyORcw9fX1UKlUUCgUaGxshEqlQkNDA5qbm6FSqaBWq9HU1AQAXD9TZ2ZmBkdHRwCAtbU17OzsuH62trawsbGBk5MTrK2twefzIRAIYG1tDQcHB9jZ2cHa2hr9+vXjhjs5OcHJyQmOjo5wdHRsN3AZ4+Oxs5Pdj4ggk8lQW1v7wN/6+nq9gLr/sVwuf+R07e3tYW1trfdhdXR0hLW1Nezt7fWGt23BPOwDDwACgQAWFhbg8XhwcnJ6YDjw4BbSw/D5fFhZWbX7WtTX17c7jfu3+O4NXoVCgdbWVgBAfX09iOihwdy2Rdn2uK6uDiqVCs3NzWhoaEBra6teuLe3BWlnZ8cFWlvXFnRtYefk5AShUAgXFxcIhUI4OztDKBS2+1owhsFC7AnJ5XJUVVVBIpGgqqoK1dXVqKmpQU1NjV5Itf1//8ssEAggFAr1vvHbPgz3f2D69eunN5zP54PP5xtpyfuu1tZWKBQK1NXVPfTL5P4vm7au7fGj3mcXFxe4uLjA2dmZCzdnZ2d4eHjAzc0N3t7ecHNzg5ubG3g8npGW3nSxELuPUqlEWVkZbt26xQWURCLR+7+qqgotLS3cc6ysrODm5gahUAhXV1e9FVUoFLJv6KfEo7a4a2pqIJPJ9PrJZDJIJBIoFAru+RYWFnBzc4OXlxfc3d3h6ekJd3d37vGAAQPg4+ODfv36GXEpe5+nLsSUSiWqqqpQXFzMdW3hVFxcjNLSUuh0OgB3w8nZ2Rmenp7w8PB45F83Nzd2gJl5Ii0tLdwXY11d3QNflvf+bWNjYwNPT0+IRCKua1sfRSIR/Pz8YGb29Fx40CdDTC6Xo7CwEEVFRbh+/ToKCwtRWFiIkpISvWMfQqEQvr6+8PHxga+vL/z8/ODr6wtfX18MGDAAQqHQiEvBMP+mUChQXl6OsrIy7m9bV1paColEAq1WC+BuyPn5+SEgIACBgYF6naenp5GXxPBMNsR0Oh1KSkqQk5PDhVRhYSGuX78OqVQK4O6WlEgkQlBQEAIDAyESieDj48OFFTuuxPQVarUaFRUVXMgVFxdzX+SFhYVoaGgAcPdkUGBgIBdwQUFBGDRoEEJDQ2FtbW3kpXgyJhFicrkcOTk5yM/PR15eHjIyMpCVlcWdkerXrx9CQkIQGhrKbV6HhIQgKCgIFhbsKhKGqaur4w6f5OXlIT8/H8XFxSgoKEBzczMsLCzg4+ODkJAQREVFITQ0FCEhIQgJCen1Jxt6XYjV1NQgLS0Nly5dQmZmJnJycnDr1i0AQP/+/REZGYnw8HCEh4cjMjISoaGhepcBMAzTeVqtFkVFRcjOzkZ2djZycnKQk5ODkpISAICjoyMiIiIQGRmJ6OhoiMViBAYGGrlqfUYNMbVajczMTKSlpXHBdfPmTfB4PAQFBSEqKgoRERGIiIhAeHg4vLy8jFUqwzxVGhoauEDLysrC1atXkZmZCZVKBWdnZ4jFYojFYsTExEAsFnPXHhpDj4aYWq3GxYsXcfr0aZw9exYZGRlQKpXo16+f3gsiFovZaWSG6WVaW1tx5coVbqPj4sWLKC0thZmZGYKDg/HCCy8gPj4eo0aN6tHPb7eHWG5uLlJSUpCSkoJffvkFTU1NEIlEGDNmDIYNGwaxWIygoKBev9/NMMyDqqurcenSJVy8eJHbMAGAqKgoxMfHIy4uDs8//3y3Xhdp8BDTarU4e/Ys9uzZg2PHjkEikaB///4YPXo04uLiEBcXhz/96U+GnCXDML1EXV0dzpw5w2243LhxA3w+H6NHj8Yrr7yCyZMnd/jTtcdlkBDT6XT47bff8NNPP2H//v2QSqWIiorC9OnTER8fj8GDBz9VF98xDHNXaWkpTp8+jaNHj+LUqVPg8XgYO3YsXn31VUycOBH29vZdnkeXQqywsBDr1q3D7t27IZFIEBkZiRkzZuDVV19lW1sMw+ipr6/HoUOHsGfPHqSkpMDCwgITJkxAUlIS4uLinviQ0mOHGBHhxIkT+PLLL3HmzBn4+fnh9ddfx4wZMxAcHPxERTBPFyJCVlYWBg0aZLIXWDJdU1tbi4MHD2L79u349ddfERgYiLfffhsLFizg2ovrNHoMhw8fpmeffZZ4PB6NGzeOjh07Rlqt9nEmwTzldu7cSf7+/gSAbt++3e64Op2OvvzyS1q9ejUNHDiQEhIS6OzZs/S3v/2N/u///q+HKma6W05ODr311ltkZ2dHrq6utGbNGmppaen08zsVYjk5OTR69Gji8Xg0ffp0unLlyhMX3FdVVVUZuwSTsXz58k6F2Mcff0yLFi0iIqLffvuNhEIhzZw5kwDQxo0be6JUpgdVV1fTypUryd7envz8/Gjfvn2del67R9uJCF999RWGDBmCpqYm/P7779i3bx+effbZJ96M7Ivq6uowe/ZsY5dhMpydnTs13tq1a+Hn5wcAGD58OGpqarB8+fJurIwxJldXV3z22We4fv06YmNj8corr2DWrFkdNqL5yBDTaDRYsGAB/vrXv+KDDz7AhQsXMHToUIMXbuqam5uRkJCA4uJiY5fSpyiVSkil0gcO9rJ22Po+T09PbNmyBadOncKvv/6KoUOHory8/JHjPzTEiAizZs3C3r17ceLECXz88ce9vr2svLw8/O1vf0NQUBAqKyuRnJwMX19fhIaG4uzZs1AqlVi2bBn+9Kc/wcfHBydPnnxgGvv378fbb7+N999/Hy+99BL+8z//k2uDvrKyEp9//jnCwsJw584djB07Fr6+vti4cSMKCgogk8mQlJSEf/7zn9z0MjIysHDhQsyaNQvR0dFYt24dNBoNN7y6uhpJSUlITk5GUlISpk6ditraWr2aOpoGEeH777/Hm2++CbFYjBdffBFFRUWdft1OnjwJCwsLWFlZ4dixY1AqlUhKSuJ++nXu3DkAQHl5OWJiYvDyyy936fW6f/kA4OjRozA3N8fkyZNx8OBBbN26FUlJSQCAvXv3IikpCWvWrGl3OdqrpSvL2J5HLeM333wDBwcHDBgwAMDdBgySk5Nhbm7ObQhcvXoVy5cvh0gkgkKhwIIFCyAUChEdHc19IXZmHKD9deBx3ofeJi4uDpcvX4a1tTVeeOEF1NTUPHzEh+1jfvvtt2RhYUFnz5411O5ut5NKpTRnzhwCQAsXLqSMjAxqaGggsVhMIpGIlixZQvn5+dTY2EjPP/88iUQived/9dVX9Pzzz1NraysREclkMgoICKCRI0eSTqejEydOUHBwMJmbm9NHH31E69evp+joaKqsrKQJEyaQn5+f3vTKysqIz+dTSUkJERHNnTuXAFBUVBS9++67REQUGxtLr776KvecyMhImj179mNNY/Xq1fTDDz8QEZFGo6GQkBByd3cnhULR6ddu5syZZGVlxT2ntbWVBgwYQHFxcXrjvfLKK3Tz5s0uv16ff/653jGxDz74gNavX683L5lMRgDok08+0eufm5v7wDGxjmp50mXsSHvL+OKLL5K3t7fe+OHh4RQTE0NERBKJhOLi4ggALVmyhPLy8igzM5Osra0pISGh0+MQtb8OtFejqaitrSWRSEQvvfTSQ4c/EGIqlYq8vLzo/fff7/biDO3bb78lAJSdnc31++ijjwgAZWZmcv1WrVpFAEgqlRLR3QOKfD6ftm3bpje9LVu2EADavn07ERHNnz+fAFBRUZHeeA8LseXLl9OAAQO4x9euXSMAtG7dOq7fqFGj6LPPPuMeJyYmUkRERKenUVlZSW5ubnpniD/88EMCQD/++GNHLxfnzJkzBIB27tzJ9Xv33XfJysqK7ty5Q0RELS0tNG3aNCLq+uvVFmJVVVX0wQcf0JEjRx6oqbMh1tlaHncZO+tRyzhlypQHQiwmJoYLMSKilStXEgCSyWRcv+HDh1NAQECnx+nMOvCoGk3JuXPnCABdvHjxgWEP7E7m5eWhsrISCxYs6Pr2YA9r2+W999cB3t7eAKB32y0fHx8AgEwmAwBcunQJCoWC699mwoQJAICzZ89y07CwsMDAgQM7rKWyshLNzc3c46CgIDg7O3PNCgHAmTNnsHLlSiiVSmzatAnp6el6z+loGhcuXIBarcaiRYuQlJSEpKQkVFVVPfa1NrGxsfD398f27du5ftnZ2dBoNNi7dy+Au7tr06dPB2C412vJkiWQy+WYOHFip2u9X2dredxl7KzHWSfu17a+3tvmnbe3N3ffz86M05l1oCs19hYjR45EYGDgQw8DPdBiYFurqO7u7t1fWQ942FXAbf3a2tIvKysDANy5c0dvPKFQCDs7O1RVVT32fMePH49du3YhNTUVY8aMQX19PRQKBcaNG8eNo9Vq8Y9//AN//PEHli5dCrFYjEuXLnV6GgUFBeDz+diwYcNj13cvHo+H1157DcnJybh9+zZu3LiB6OhomJubY8eOHVi4cCH279+PnTt3AjDc62VnZ4cNGzZgzpw5T3zSqLO1PO4ymgpDrQOmwMPDg8unez2wJRYQEAAAuHLlSvdX1Uv4+/sDwCPPMD7JLxESExOxYcMGzJ07F6tWrcJ7772H3bt3Y9iwYQDuBuj48eORn5+P/fv3Y+TIkY89DTs7O1RUVKCiouKB5z7yIOgjvPbaa9DpdNi9eze+/fZbvPPOO3jttddw/vx5nDlzBh4eHtw3u6Fer08//RTBwcGYOXNmh6fRH+VxanmcZTQVhlwHejOVSoXc3NyHNsj4QIiJRCK88MILSE5O5rZU+rqhQ4fCwcEBhw4d0utfUVGB5uZmTJo0qd3nm5mZcU1lt1Gr1SgqKkJWVhaSk5OxefNmTJkyhRuenp6OU6dOITY2Vu85dM+vwDqaRnh4OIgIK1as0Jv3zZs3sXbt2k4vPwD4+fkhNjYW//u//wtbW1t4enpi6tSpsLe3R2JiIubNm8eN29XXq42NjQ22b98OiUTCnY1sQ538Ndzj1PI4y9hVFhYWaGpq4m7eAQBNTU0G/0wZch3ozb755hsoFAokJCQ8MOyhl1h88cUXuHDhAlauXNntxRlS280Q7r0Eoa1f2/EvANzxhLZT8M7OzlizZg1+//13pKamcuN9/fXXeO211zBq1CgA4FbK+7caPD09IZPJkJGRgXPnzqG5uRlr1qzBL7/8gtOnT+PcuXP4448/uCZ/gX/v0m7duhU5OTnYvHkz8vLyUF1djezsbFRXV3c4jfj4eDz33HPYtWsXpk+fjh07dmDt2rVYtGgRlixZ8tiv37x581BSUoKlS5cCuPst/8orr0AoFGLIkCHceF19vdrutajRaPDMM8/g73//O/bt24fVq1dz47RtWdx7TBD49/vZNo3O1vK4y9hZj1rG8PBw1NfXY/Xq1SgsLMQnn3wClUqF69evIzMzEwC4u7vfu75KpVK9Ze5onM6sA4+q0VSkpqZi5cqV+PDDD+Hm5vbgCI86G7Bt2zYyNzenxYsXk0ql6razDoaSmppKERERBIASExPpxo0bdO7cOXr22WcJAI0bN46ys7Pp/PnzNHjwYAJAs2fP1judfujQIXrxxRfp7bffplWrVtEXX3zBnaJfv349ubi4EACaM2eO3k+vsrKyyNvbmwIDA2nv3r1ERHT06FESCAQEQK8LDQ3lTm8vXryYBAIBxcTEUEpKCv38888kFArp5Zdfpqampk5No7a2lhITE8nV1ZVcXFxo7ty5T3z6vKWlhZYuXarXLzMzkzvDd78neb127txJAQEBBIAWLVpEhYWFdOnSJTI3NycANH/+fPrpp5+4nxf5+/vTzp07qb6+ntLS0uill14iADR48GA6fvx4p2rpyjK2p711Qi6X08SJE8ne3p5iYmLo8uXL9Prrr9Ps2bPpyJEjlJKSQn5+fgSA3nrrLZJKpbRt2zayt7cnAPTxxx/TyZMnOxxHo9G0uw60V6Mp2L17N1lbW9Ps2bMf+n4SEbXbisWhQ4cwZ84cBAYGYtu2bQgNDTVIsj4NDh8+DLVajbi4ONTU1KCmpgYVFRXIzs4GEeHTTz/tkWkwjCmSy+VYtmwZtmzZgnfffRdffPHFo9sk7CgJi4qKSCwWk6WlJb3zzjt616swD5eVlUVeXl4PHVZXV0f/+te/emQaQqGww+5h12gx7LUzFrVaTd9//z25uLiQq6srHT58uMPndKoVC61WSxs2bCB3d3cSCAS0fPlykkgkXS64r9q6dSsBoOTkZMrIyKDm5maSSqV09OhRWrp0aaeupjfENBjGVCiVSlq3bh2JRCKytLSkZcuWUV1dXaee+1jtiTU2NtJ///d/k7u7O7efeuHChScqui/TaDT04Ycfkru7OwEge3t7io6Opi1btnS6/TVDTINhervy8nL6j//4D3JzcyNra2tauHBhp3/21eaJmqdWKpXYuXMnvvvuO2RkZCAkJAQzZszAjBkzMGjQoK7sCvc5zc3NsLW17dLdnAwxDYbpLWpra3HgwAHs2bMHZ8+ehYuLC5KSkrB48WJ4eno+9vS6fKOQ9PR07Ny5E/v27UNVVRUiIiK4QGu7cJZhmKdbXV0dDh48iD179iA1NRVWVlYYP348EhISMHHixC41sWSwW7bpdDqcP38ee/bswb59+1BdXY3w8HDu3nMvvPAC+Hy+IWbFMEwvp9PpcOXKFZw+fRopKSk4f/48zMzM8NJLL2HGjBmYOHGiwfKgW26eq9Vq8csvv+DYsWNISUlBbm4uLC0t8fzzzyMuLg7x8fGIiorq9W2UMQzTecXFxUhJScHp06dx5swZ3LlzBx4eHoiLi8NLL72ECRMmQCAQGHy+3X4HcAC4ffs2t3ApKSmoqqqCk5MTYmJiIBaLub89eetzhmGenEqlQmZmJtLS0pCWloYLFy6grKwMfD4fI0eO5PbAwsLCur2WHgmx++Xl5eHMmTO4dOkS0tLScPPmTa6VTbFYDLFYjKFDhyIsLEyvCRKGYYyjtLQUly5d4j6zmZmZUKlUcHZ25j6zI0eOxNChQ3u8CXGjhNj95HI5Ll++jPPnzyMjIwMXLlzAnTt3YGlpiYCAAISGhiIkJARRUVEIDQ2FSCQydskM0ye1traiqKgIGRkZyM/PR15eHtLT0yGVSmFhYYHAwEBERUVh+PDhGDZsGEJCQox+1rxXhNj9dDodCgoKcOXKFeTk5CArKws5OTmQSCQAADc3N4SHhyMyMhJhYWEIDg5GQEBAp++iwzBPO6VSicLCQhQVFSE3N5f7nBUXF0On00EgECAsLAwRERGIiIhAZGQkBg8e3CubKuqVIfYoMpmMC7Ts7GxkZ2cjPz8fLS0tAO62aBAQEIDAwECuCwgIQEBAADszyjx1NBoNSktLUVhYyHVFRUUoLCzErVu3QEQwNzeHSCRCZGQkwsPDudDy9/c3+hZWZ5lUiD2MTqdDeXk5ioqKUFRUhOvXr3NvVmlpKdeek7e3N0QiEXx9feHn5wdfX1/4+PjA19cXvr6+sLa2NvKSMMzj0el0qKqqQmlpKcrKyriuvLwcxcXFKCkpgVqtBnB37yUoKEjviz0wMBADBw40+XXf5EOsPa2trSguLsb169dRVFSEkpISlJWVcW/6vQ0Zenh4cIHm6+uLAQMGwMvLC25ubvD29oabm5vJv9mM6dDpdKiursbt27dRVVWF27dv49atW9y6W15ejlu3bnEhZWlpiQEDBnDrr7+/v15YOTg4GHmJuk+fDrGO1NbW6n2D3fuNVlFR8UDzvkKhEO7u7vD09OT+enh4cJ1QKIRQKISzs7PJbIozPauxsREymQxSqRRSqRRVVVWQSCR6XVVVFaRSqV5DiHw+Hz4+Pnp7D/fuVXh6ej66qZo+7qkOsY6oVCpUV1ejsrISUqkUt27dglQqRUVFBde/uroa1dXVes0pm5mZcWHWFmxCoRCurq5cv7a/Tk5OcHR0hKOjI2xsbIy4tMzj0Gq1kMvlqK+v5zqpVAqZTIba2lrIZDLIZDKuHbi2fm2tCbdxdHSEl5cX96V475b/vXsC7Jjuo7EQMwCNRsOtwG3fsveuyLW1tdzK3PZYqVQ+MB1ra2su0BwdHeHk5KQXcm2dra0t+vXrB2tra9jZ2cHBwQHW1tYQCASws7ODtbU1u3D4IZqamqBSqSCXy9HS0gKlUgm5XA6VSoWmpiY0NTWhpaUFcrkccrkcdXV13P/3d/ffUwG4e3u1+7+kXFxc4OLi8sCXmqurK1xdXXvl2T5Tw0LMSJqamiCTyR75Ibn3W14ul6OhoYHr39zcjPr6+g5vpvGwkAMAe3t77j6cbWFnZWXFfdvz+XzugsX7w5DH48HJyemR8+xouEKhQGtr62MNb2pq4m6i0tZOfGtrK9fOfttw4O4PjYG7lxC0BVJbSHVEIBDA1tZW70vk/i+Q+7t7v2iEQmGH82AMj4WYCWv7IDc0NEClUqGxsREKhQIqlQr19fXcB7m+vh4qlYr70Dc0NECr1UKr1XI33mgbF7h73Eaj0UCn03E3qmijUqkeuHnHw2p6lHvD8mEsLS1hb2+v169t6xIAnJycwOPxYGFhwYWyra0ttyvu6OgIMzMzbj4CgQA2NjYQCATg8/mwtraGk5MTbGxsuMCysbFhu2smjIUY0200Gg0sLS2xf/9+TJs2zdjlMH3U03k6g2GYPoOFGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJo2FGMMwJs3C2AUwfUd2djbUajX3WKvVAgBu3ryJjIwMvXGDg4PB5/N7tD6mb+IRERm7CKZvmDRpEo4ePdrheLa2tpBKpbC3t++Bqpi+ju1OMgYzc+ZM8Hi8dscxNzfHxIkTWYAxBsNCjDGYyZMnw9raut1xdDodZs+e3UMVMU8DFmKMwdjZ2WHKlCmwtLR85Dj29vZ48cUXe7Aqpq9jIcYYVGJiot7B/XtZWloiISGhw601hnkc7MA+Y1BqtRpCoRANDQ0PHX727FnExsb2bFFMn8a2xBiDatvasrKyemCYi4sLRowYYYSqmL6MhRhjcDNnzkRra6teP0tLS8yZMwfm5uZGqorpq9juJGNwOp0Onp6eqK6u1ut/+fJlDBkyxEhVMX0V2xJjDM7MzAyzZ8/W26X08fFhAcZ0CxZiTLe4d5fSysoK8+bNM3JFTF/FdieZbiMSiVBSUgIAKCgoQHBwsJErYvoitiXGdJu5c+cCAMLDw1mAMd2GtWLBPJHGxkaua2pqglKpREtLCzdcqVSiX79+AIDIyEgcOHAADg4O3HALCwsIBAJYW1tDIBDAwcEBTk5OHf72kmHux3YnGQB3Q6ekpARlZWWorq6GVCqFRCJBTU0Nqqurcfv2bcjlcjQ2NqKurq7b6rC3t4dAIICjoyNcXFzg7u4ONzc3uLq6cv97eXnB398f/fv377Y6GNPBQuwp0tLSgoKCAuTl5aGwsBAlJSVcJ5FIuPFsbGzg6uoKT09PuLi4wM3NDe7u7nBycoJAIEC/fv0gEAi4wBEIBLC0tNRrmcLKygp8Ph8XL17E0KFD0draCoVCwQ1XqVRobm6GUqnktujq6+v1/q+pqYFEIkF1dTX3/72/BHB0dIS/vz/XDRw4ECEhIQgLC4Ozs3PPvKiM0bEQ66PKy8uRlpaGzMxM5OfnIzc3FyUlJdDpdLCyssLAgQMhEon0QsDf3x++vr5wcnIydvmPpFQqUV5erhfAbd2NGzdQX18PAHBzc0NYWBhCQkIQERGB6OhohIaGsott+yAWYn1AS0sLLl26hEuXLiEtLQ3p6emQSCQwNzdHcHAwQkND9bqBAwfCwqJvHg6tqKjgQrvtb25uLhQKBezt7REVFQWxWAyxWIzhw4fD1dXV2CUzXcRCzARptVpcvXoVKSkpSElJwfnz56FUKuHh4YGoqCiuGz58OHdw/Wmm1Wpx7do1ZGRkICMjA7///juuXr0KrVYLkUiEuLg4xMXFIT4+vldvhTIPx0LMRDQ2NuLnn3/GgQMHcPLkScjlcnh6emLMmDEYM2YMRo8ejQEDBhi7TJPR2NiIX375BampqThz5gxycnJgbm6OYcOGYerUqZg6dSp8fHyMXSbTCSzEerGGhgYcOHAABw4cwOnTp6HRaDBy5EhMmjQJ8fHxGDRokLFL7DOkUilSU1Nx7NgxHD9+HA0NDYiKisL06dMxY8YMiEQiY5fIPAILsV4oIyMD69evx65du6BWqzFixAhMmDABCQkJcHNzM3Z5fZ5Wq8XFixexd+9e7N27FxKJBFFRUVi4cCESExPZXZp6GRZivURjYyPWr1+PdevWoaioCFFRUXjjjTcwa9YsdpzGiDQaDU6cOIHNmzfj+PHj4PP5mD17NpYtW8a2znoJFmJGJpPJ8PXXX+Pbb7+FWq3G66+/jvnz5yMyMtLYpTH3qa6uxrZt2/D999+jvLwcM2bMwIoVKxAREWHs0p5uxBiFXC6nv/71r8Tn80koFNJ//dd/UW1trbHLYjpBrVbTztfwBMUAAA6DSURBVJ07KSIigng8Hk2aNImuXbtm7LKeWizEephWq6VNmzaRu7s7OTs701dffUVNTU3GLot5Ajqdjo4dO0bPPPMMWVpa0nvvvUf19fXGLuupw3Yne9C1a9cwd+5cZGZm4s0338THH3/Mfv/XB2i1WmzcuBGrVq0Cj8fD2rVrMX36dGOX9dRgTfH0kC1btmDIkCEwMzPD1atX8fXXX7MA6yPMzc2xaNEiFBYWYurUqXjllVewePFivVY9mO7DtsS6WWtrKxYsWIAdO3Zg+fLl+OSTT9q9uSxj+g4cOIAFCxbAy8sLR48ehZ+fn7FL6tNYiHUjpVKJl19+Gb/99hv27NmDsWPHGrukdjU2NkIgEBi7jD6hvLwcU6dORU1NDVJSUhAYGGjskvostjvZTVpaWjBhwgRcvHgRKSkpvTrAvv32W4wYMQIxMTHGLkXPoUOHMGDAABQUFBi7lMfm4+OD1NRUeHt7Y+TIkSa5DKaChVg3+ctf/oIrV67gzJkzeO6554xdTrsWLVoEuVwOnU5nkOnd2zZZV/D5fLi6usLGxsYg0+tpTk5OOHXqFEQiEaZNm6bXnhpjOCzEusHBgwfx3XffYe3atSZx0aqFhQW8vLwMMq26ujrMnj3bINOKj49HRkYG/P39DTI9Y7C3t8eePXsgk8mwdOlSY5fTJ7EQM7DGxkYsWrQICxcuREJCgrHL6VHNzc1ISEhAcXGxsUvpVby8vLB582Zs3rwZp0+fNnY5fQ4LMQNbt24dWltbsXr16m6fV1ZWFmJjY8Hj8TB69GhIJBJ89dVXsLGxweeffw61Ws2Nm5GRgYULF2LWrFmIjo7GunXroNFoHpjmuXPnMG7cOPTv3x9jx459rEA6ePAgCgoKIJPJkJSUhH/+85+orKzE559/jrCwMNy5cwdjx46Fr68vamtrUV1djaSkJCQnJyMpKQlTp05FbW0tgLtbdJs2bUJ8fDwOHToEALh69SqWL18OkUgEhUKBBQsWQCgUIjo6Wq9OIsL333+PN998E2KxGC+++CKKiooAoN16utPEiRMxbtw4fPrpp906n6eSES+07ZMiIiLozTff7LH51dbWkoeHBwUEBJBGo6EVK1bQjh079MYpKysjPp9PJSUlREQ0d+5cAkBRUVH07rvvEhHRuHHjyNnZmd544w06ceIEffHFF2RlZUWenp6kUCg6Xc+ECRPIz8+Pe3zixAkKDg4mc3Nz+uijj2j9+vUUHR1NlZWVFBsbS6+++io3bmRkJM2ePZuIiPLz82nZsmUEgPbt20dERBKJhOLi4ggALVmyhPLy8igzM5Osra0pISGBm87q1avphx9+ICIijUZDISEh5O7uTgqFot16utvx48eJx+Nx7wNjGCzEDKi2tpZ4PB4dP368R+e7a9cuAkCrVq2iadOmPTB8+fLlNGDAAO7xtWvXCACtW7eO6zdu3Djy9PTUe97q1asJAP3rX//qdC33hxgR0fz58wkAFRUV6fUfNWoUffbZZ9zjxMREioiI4B6fO3dOL8SIiFauXEkASCaTcf2GDx9OAQEBRERUWVlJbm5upNVqueEffvghAaAff/yx3Xq6m0qlIhsbGy5gGcPomw2tG8mNGzdARAgJCenR+c6cORMbNmxAcnIysrOzHxheWVmJ5uZm7nFQUBCcnZ1x69YtvfHuvS8kcPfmtytXrkRGRkaX6rO0tISFhQUGDhyo1//MmTMA7l5Pt3PnTqSnp4PuuWzxYfcBaLvRx73DvL29cePGDQDAhQsXoFarsWjRIr3nLViwALa2tu3W092srKwQEBDA1coYBgsxA2oLCjs7ux6f9+uvv46zZ89i06ZN+J//+R+9YePHj8euXbuQmpqKMWPGoL6+HgqFAuPGjWt3mp6enrC1te22n89otVr84x//wB9//IGlS5dCLBbj0qVLXZpmQUEB+Hw+NmzYYKAqDYvP57NLLQyMhZgBtd3rsKampkfvoqNQKLBr1y4kJibim2++wbx58/Qu7UhMTERLSwvmzp2LN954A5WVldi9ezeGDRvW4bR5PB7CwsIMXrNOp8P48ePh6uqK/fv3AwA2btzY5ena2dmhoqICFRUV8Pb21htWU1MDFxeXLs+jK6RSKbsnpoGxs5MGFBQUBBsbG6Snp/fofFetWoW//OUv+PLLLyEQCPDWW2/p7Zap1WoUFRUhKysLycnJ2Lx5M6ZMmdLhdEtLS6FWqzFjxoxO12JmZoampqYOx0tPT8epU6cQGxurVyd18Vdw4eHhICKsWLFCr//Nmzexdu3aLk27q2pqalBSUoJnnnnGqHX0NWxLzICsrKwQFxeHn376CfPmzeuReaalpeHWrVuIj48HACQnJ+Odd97BunXrsHjxYgDAmjVr8Msvv+CZZ56Bh4cH7O3t4ezsrHcRqbm5Oerq6qBQKMDn80FESE5OxkcffYTg4OBO1+Pp6QmZTIaMjAw0NjYiOjoaTU1N0Gq1qK+v55ra5vF4AICtW7ciOjoaly9fRl5eHqqrq5GdnQ03Nzfuyv+amhpu+nK5HAD0Lg+RSqXcrnx8fDyee+457Nq1C0qlElOnTuVuuPLjjz8CwEPr6Qk//fQT7OzsMHLkyB6b51PBmGcV+qIjR44Qj8ejP/74o9vnlZqaSt7e3vTee++RTqcjIqIdO3YQALKysqJvvvmGiIiOHj1KAoGAAOh1oaGh3KUF2dnZlJCQQGPHjqWFCxfSn//8Z72zgp2VlZVF3t7eFBgYSHv37qX169eTi4sLAaA5c+bQlStXuHEXL15MAoGAYmJiKCUlhX7++WcSCoX08ssv05EjR+iFF14gADRkyBA6deoUpaSkkJ+fHwGgt956i6RSKW3bto3s7e0JAH388cek0WiotraWEhMTydXVlVxcXGju3LnccrZXT3dSqVTk7+9PixYt6pH5PU1YKxYGRkQYMWIEGhsbkZaW1it+93f48GGo1WrExcWhpqYGNTU1qKioQHZ2NoiIXYDZA95//32sX78eubm57H6WhmbcDO2bysvLqX///rRkyRJjl0JZWVnk5eX10GF1dXWdvgZMKBR22B05csSQpfcZJ06cIB6PR9u2bTN2KX0SC7FusmfPHuLxeLRmzRqj1rF161YCQMnJyZSRkUHNzc0klUrp6NGjtHTp0se6Gp95fBcuXCAnJyd67bXXjF1Kn8VCrBt99913ZGZmRitWrDBaDRqNhj788ENyd3cnAGRvb0/R0dG0ZcsWvavaGcP79ddfSSAQ0IQJE6ilpcXY5fRZLMS62caNG8nMzIzmz59v9K0ehULBnQBguteGDRvI1taWXn31VWptbTV2OX0aO7DfA44ePYp58+bB1dUVu3fvNok2xpgnU19fj4ULF2L//v1YsWIFkpOTuZ9KMd2DXezaAyZOnIirV6/C1dUVMTEx+Pvf/673W0amb9i3bx8iIyPx+++/4/Tp0/jss89YgPUAFmI9xNvbG6mpqUhOTsaXX36JQYMG4aeffuryFeqM8WVnZ2PUqFGYMWMGYmNjcfXqVYwePdrYZT01WIj1IHNzc7z//vu4fv064uLiMGvWLAwdOhRHjhxhYWaCcnNzMWfOHAwePBgtLS24ePEitm7davTfZz5tWIgZgbu7OzZt2oT09HS4ublhypQpCA8Px7Zt2/RaY2V6pwsXLmDSpEmIiIjA1atXsX37dly8eBFisdjYpT2VWIgZUVRUFA4fPozCwkIMGzYMCxYsgI+PD/785z8jNzfX2OUx95DL5Vi/fj2GDx+OYcOGobKyEj/88AOysrIwc+ZM7regTM9jZyd7kbKyMmzatAlbtmxBZWUlRowYgTfeeAOTJ0/u0R8qM3dpNBqcPXsW27Ztw/79+2FmZoaXX34ZCxcuxPPPP2/s8pj/j4VYL6TVanHq1Cls2rQJR48eBRFh1KhRmDZtGiZPngx3d3djl9hntbS04NSpUzhw4ACOHTuGO3fuIDo6GvPnz0dCQsIDrd8yxsdCrJeTy+U4duwYDh48iBMnTkCpVEIsFiM+Ph5jxoxBTEwMrKysjF2mScvLy0NqairXtbS0ICYmBtOmTcPUqVMhEomMXSLTDhZiJqSlpQUnT57E8ePHkZqaipKSEvD5fIwYMQKjR4/G0KFDMXjwYKM0j20qtFotCgoKkJaWhnPnziE1NRUSiQROTk6IjY3F2LFjMXnyZHh4eBi7VKaTWIiZsOLiYqSmpiIlJQXnzp2DVCqFhYUFwsLCIBaLIRaLMXjwYAwaNOip3FojIpSUlODq1atIT09HWloa11ijnZ0dhg4dijFjxmDMmDGIiopiF6aaKBZifUhJSQnS0tKQnp6O9PR0XLlyBS0tLdydfcLCwhAaGoqwsDAEBwfD398ffD7f2GV3mVqtRnl5OW7cuIGcnBzk5+cjNzcX+fn5UCgUMDMzw6BBgxAdHc2Fe1hY2EPvpsSYHhZifZhGo0FBQQHy8/P1PtzFxcXQarUAABcXF/j7+8Pf3x9+fn7w8fGBl5cXXFxc4O7uDnd3d6PunqrVakilUlRXV0MikaCmpgZlZWUoLS1FSUkJSktLUVFRwS2Ph4cHF9QhISEIDw9HaGgoBAKB0ZaB6V4sxJ5CSqUSN27cQElJCde1hUJ5eTnq6ur0xufz+fDw8ICTkxOcnJzg4OAAe3t7CAQCCAQC7vIPgUDAbd3weDy9y0IUCgVaW1u5x/X19SAiKBQKNDY2orGxEQ0NDZDL5WhsbIRcLodUKoVMJtOrxc7ODt7e3lzo3hvAAwcORP/+/bvrZWN6KRZizANUKhVqamogkUhQXV2NmpoaVFVVoaGhAfX19WhoaOCCpy1wgH8HE3B3K7CxsZGbpo2NDXfzWuDfgWdnZ8eFoaOjIxwdHSEQCODg4AAXFxd4enrC1dUVrq6u3E1OGOZeLMQYhjFp7GdHDMOYNBZiDMOYNBZiDMOYNAsAe41dBMMwzJP6fw7gk/9esjKuAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "import json\n",
    "from gquant.dataframe_flow import (TaskSpecSchema, TaskGraph)\n",
    "\n",
    "from mortgage_common import (\n",
    "    mortgage_etl_workflow_def, generate_mortgage_gquant_run_params_list,\n",
    "    MortgageTaskNames)\n",
    "\n",
    "start_year = 2000\n",
    "end_year = 2001  # end_year is inclusive\n",
    "# end_year = 2016  # end_year is inclusive\n",
    "part_count = 2  # the number of data files to train against\n",
    "\n",
    "# ADJUST YOUR MORTGAGE DATAPATH IF DIFFERENT\n",
    "mortgage_data_path = './mortgage_data'\n",
    "\n",
    "gquant_task_spec_list = mortgage_etl_workflow_def()\n",
    "mortgage_run_params_dict_list = generate_mortgage_gquant_run_params_list(\n",
    "    mortgage_data_path, start_year, end_year, part_count, gquant_task_spec_list)\n",
    "\n",
    "mortgage_run_params_dict_list_for_printing = mortgage_run_params_dict_list.copy()\n",
    "for iparams_dict in mortgage_run_params_dict_list_for_printing:\n",
    "    iparams_dict['task_spec_list'] = 'This is gquant_task_spec_list'\n",
    "\n",
    "print('Parameters configuration for Mortgage Workflow Runner '\n",
    "      '(shortened to 2 files)')\n",
    "print(json.dumps(mortgage_run_params_dict_list_for_printing, indent=2))\n",
    "\n",
    "# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #\n",
    "# ADJUST PART_COUNT FOR YOUR SYSTEM MEMORY\n",
    "part_count = 12  # the number of data files to train against\n",
    "# part_count = 4  # the number of data files to train against\n",
    "\n",
    "mortgage_run_params_dict_list = generate_mortgage_gquant_run_params_list(\n",
    "    mortgage_data_path, start_year, end_year, part_count, gquant_task_spec_list)\n",
    "\n",
    "_basedir = os.path.abspath('')  # path of current notebook\n",
    "mortgage_lib_module = os.path.join(_basedir, 'mortgage_gquant_plugins.py')\n",
    "\n",
    "mortgage_workflow_runner_task = {\n",
    "    TaskSpecSchema.task_id:\n",
    "        MortgageTaskNames.mortgage_workflow_runner_task_name,\n",
    "    TaskSpecSchema.node_type: 'MortgageWorkflowRunner',\n",
    "    TaskSpecSchema.conf: {\n",
    "        'mortgage_run_params_dict_list': mortgage_run_params_dict_list\n",
    "    },\n",
    "    TaskSpecSchema.inputs: [],\n",
    "    TaskSpecSchema.filepath: mortgage_lib_module\n",
    "}\n",
    "\n",
    "# Can be multi-gpu. Set ngpus > 1. This is different than dask xgboost\n",
    "# which is distributed multi-gpu i.e. dask-xgboost could distribute on one\n",
    "# node or multiple nodes. In distributed mode the dmatrix is distributed.\n",
    "ngpus = 1\n",
    "xgb_gpu_params = {\n",
    "    'nround':            100,\n",
    "    'max_depth':         8,\n",
    "    'max_leaves':        2 ** 8,\n",
    "    'alpha':             0.9,\n",
    "    'eta':               0.1,\n",
    "    'gamma':             0.1,\n",
    "    'learning_rate':     0.1,\n",
    "    'subsample':         1,\n",
    "    'reg_lambda':        1,\n",
    "    'scale_pos_weight':  2,\n",
    "    'min_child_weight':  30,\n",
    "    'tree_method':       'gpu_hist',\n",
    "    'n_gpus':            ngpus,\n",
    "    # 'distributed_dask':  True,\n",
    "    'loss':              'ls',\n",
    "    # 'objective':         'gpu:reg:linear',\n",
    "    'objective':         'reg:squarederror',\n",
    "    'max_features':      'auto',\n",
    "    'criterion':         'friedman_mse',\n",
    "    'grow_policy':       'lossguide',\n",
    "    'verbose':           True\n",
    "}\n",
    "\n",
    "xgb_trainer_task = {\n",
    "    TaskSpecSchema.task_id: MortgageTaskNames.xgb_trainer_task_name,\n",
    "    TaskSpecSchema.node_type: 'XgbMortgageTrainer',\n",
    "    TaskSpecSchema.conf: {\n",
    "        'delete_dataframes': False,\n",
    "        'xgb_gpu_params': xgb_gpu_params\n",
    "    },\n",
    "    TaskSpecSchema.inputs: [\n",
    "        MortgageTaskNames.mortgage_workflow_runner_task_name\n",
    "    ],\n",
    "    TaskSpecSchema.filepath: mortgage_lib_module\n",
    "}\n",
    "\n",
    "task_spec_list = [mortgage_workflow_runner_task, xgb_trainer_task]\n",
    "task_graph = TaskGraph(task_spec_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Refer to `MortgageWorkflowRunner` and `XgbMortgageTrainer` in the `mortgage_gquant_plugins.py` module for the details of the mortage workflow runner and xgboost trainer tasks.\n",
    "\n",
    "Note the novel manner in which gQuant is used. The `mortgage_workflow_runner` actually runs another gQuant workflow defined by `mortgage_etl_workflow_def()` for each set of acquisition and performance csv files. The output from `mortgage_workflow_runner` is a pandas dataframe (concatenated from processing multiple `final_per_acq_df` dataframes). The `xgb_trainer` is used in an atypical manner. It does not output a dataframe, instead it produces an XGBoost booster. Even though we mostly focus on dataframe flow with gQuant, if the tasks input/output something beside dataframes then gQuant will still run the workflow. When a task does not output a dataframe then gQuant does not perform columns validation. Currently, the responsibility is on the end-user to validate or make sure the input/output types match for the wired non-dataframe tasks. Above only the `xgb_trainer` task's output is not a datframe.\n",
    "\n",
    "We can run the workflow now and obtain the XGBoost trained booster. You can monitor the GPU utilization in a terminal using `nvidia-smi`. On my node I have 125GB of host RAM and two 16GB GPU cards. I am able to process 12 dataframes. If I load more than 12 dataframes, then the workflow crashes during DMatrix creation due to out of memory error. The DMatrix instantiation seems to inflate the data temporarily and I run out of memory on the host. In a terminal you can watch with nvidia-smi the utilization on the GPU. During XGBoost training I typically observe:\n",
    "\n",
    "```\n",
    "$ nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n",
    "pid, process_name, used_gpu_memory [MiB]\n",
    "27774, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 11025 MiB\n",
    "\n",
    "$ watch -n 0.5 nvidia-smi pmon -c 1\n",
    "# gpu        pid  type    sm   mem   enc   dec   command\n",
    "# Idx          #   C/G     %     %     %     %   name\n",
    "    0      27774     C    99    28     0     0   python\n",
    "    1          -     -     -     -     -     -   -\n",
    "\n",
    "```\n",
    "\n",
    "The DMatrix occupies 11GB of GPU memory and XGBoost training is utilizing 99% of compute processing power on the GPU (specifying `ngpus=2` will split the training across two GPUs)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mortgage_workflow_runner:INFO: TRYING TO LOAD 12 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q1.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q1.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 1 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q2.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q2.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 2 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q3.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q3.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 3 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q4.txt_1\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q4.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 4 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2000Q4.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2000Q4.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 5 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q1.txt_1\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q1.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 6 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q1.txt_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q1.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 7 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_1_1\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 8 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_1_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 9 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_0_1\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 10 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_0_0\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 11 FRAMES\n",
      "perfdata:INFO: LOADING: ./mortgage_data/perf/Performance_2001Q3.txt_1_1\n",
      "acqdata:INFO: LOADING: ./mortgage_data/acq/Acquisition_2001Q3.txt\n",
      "mortgage_workflow_runner:INFO: LOADED 12 FRAMES\n",
      "mortgage_workflow_runner:INFO: HOST RAM (MB) TOTAL 128904; USED 17461; FREE 93503\n",
      "mortgage_workflow_runner:INFO: RUN PYTHON GARBAGE COLLECTION TO MAYBE CLEAR CPU AND GPU MEMORY\n",
      "mortgage_workflow_runner:INFO: HOST RAM (MB) TOTAL 128904; USED 17460; FREE 93504\n",
      "mortgage_workflow_runner:INFO: USING ARROW\n",
      "mortgage_workflow_runner:INFO: ARROW TO PANDAS\n",
      "mortgage_workflow_runner:INFO: HOST RAM (MB) TOTAL 128904; USED 32872; FREE 78092\n",
      "xgb_trainer:INFO: JUST BEFORE DMATRIX\n",
      "xgb_trainer:INFO: HOST RAM (MB) TOTAL 128904; USED 17559; FREE 93405\n",
      "xgb_trainer:INFO: CREATING DMATRIX\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/lib/python3.6/site-packages/xgboost-0.83.dev0-py3.6.egg/xgboost/core.py:604: FutureWarning: Series.base is deprecated and will be removed in a future version\n",
      "  if getattr(data, 'base', None) is not None and \\\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "xgb_trainer:INFO: JUST AFTER DMATRIX\n",
      "xgb_trainer:INFO: HOST RAM (MB) TOTAL 128904; USED 63791; FREE 47174\n",
      "xgb_trainer:INFO: CLEAR MEMORY JUST BEFORE XGBOOST TRAINING\n",
      "xgb_trainer:INFO: HOST RAM (MB) TOTAL 128904; USED 48713; FREE 62252\n",
      "xgb_trainer:INFO: RUNNING XGBOOST TRAINING\n",
      "XGBOOST BOOSTER:\n",
      " <xgboost.core.Booster object at 0x2b21119dcef0>\n"
     ]
    }
   ],
   "source": [
    "out_list = [\n",
    "    MortgageTaskNames.mortgage_workflow_runner_task_name,\n",
    "    MortgageTaskNames.xgb_trainer_task_name\n",
    "]\n",
    "((mortgage_feat_df_pandas, delinq_df_pandas), bst,) = \\\n",
    "    task_graph.run(out_list)\n",
    "# print(mortgage_feat_df_pandas.head())\n",
    "# print(delinq_df_pandas.head())\n",
    "\n",
    "print('XGBOOST BOOSTER:\\n', bst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Specifying ngpus=2 will split the training across two GPUs, but in a non-distributed manner (this approach is being [deprecated](https://xgboost.readthedocs.io/en/latest/gpu/#single-node-multi-gpu) in favor of distributed). If you would like to run with 2 GPUs in this manner, convert the cell below to code (from raw format to code select cell and press Esc+y), and run below on 2 GPUs. If you don't have at least two GPUs do not run the cell below."
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "# CLEAN MEMORY FROM RUN BEFORE\n",
    "import gc\n",
    "from contextlib import suppress\n",
    "\n",
    "with suppress(Exception):\n",
    "    del(bst)\n",
    "\n",
    "gc.collect()\n",
    "\n",
    "ngpus = 2\n",
    "xgb_gpu_params = {\n",
    "    'nround':            100,\n",
    "    'max_depth':         8,\n",
    "    'max_leaves':        2 ** 8,\n",
    "    'alpha':             0.9,\n",
    "    'eta':               0.1,\n",
    "    'gamma':             0.1,\n",
    "    'learning_rate':     0.1,\n",
    "    'subsample':         1,\n",
    "    'reg_lambda':        1,\n",
    "    'scale_pos_weight':  2,\n",
    "    'min_child_weight':  30,\n",
    "    'tree_method':       'gpu_hist',\n",
    "    'n_gpus':            ngpus,\n",
    "    # 'distributed_dask':  True,\n",
    "    'loss':              'ls',\n",
    "    # 'objective':         'gpu:reg:linear',\n",
    "    'objective':         'reg:squarederror',\n",
    "    'max_features':      'auto',\n",
    "    'criterion':         'friedman_mse',\n",
    "    'grow_policy':       'lossguide',\n",
    "    'verbose':           True\n",
    "}\n",
    "\n",
    "# By loading existing dataframes no need to re-run\n",
    "# mortgage_workflow_runner task.\n",
    "replace_spec = {\n",
    "    MortgageTaskNames.mortgage_workflow_runner_task_name: {\n",
    "        'load': [mortgage_feat_df_pandas, delinq_df_pandas]\n",
    "    },\n",
    "    MortgageTaskNames.xgb_trainer_task_name: {\n",
    "        TaskSpecSchema.conf: {\n",
    "            'delete_dataframes': False,\n",
    "            'xgb_gpu_params': xgb_gpu_params\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "out_list = [MortgageTaskNames.xgb_trainer_task_name]\n",
    "(bst,) = task_graph.run(out_list, replace=replace_spec)\n",
    "\n",
    "print('XGBOOST BOOSTER:\\n', bst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "During XGBoost training on 2 GPUs above, I typically observe (pid differs from run to run):\n",
    "```\n",
    "$ nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n",
    "pid, process_name, used_gpu_memory [MiB]\n",
    "12819, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 5965 MiB\n",
    "12819, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 5905 MiB\n",
    "\n",
    "\n",
    "$ watch -n 0.5 nvidia-smi pmon -c 1\n",
    "# gpu        pid  type    sm   mem   enc   dec   command\n",
    "# Idx          #   C/G     %     %     %     %   name\n",
    "    0      12819     C    98    13     0     0   python\n",
    "    1      12819     C    99    13     0     0   python\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "              total        used        free      shared  buff/cache   available\n",
      "Mem:         128904       49537       61426        6327       17940       69574\n",
      "Swap:             0           0           0\n",
      "\n",
      "pid, process_name, used_gpu_memory [MiB]\n",
      "8165, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 11071 MiB\n"
     ]
    }
   ],
   "source": [
    "# DISPLAY CURRENT MEMORY ON HOST AND GPU\n",
    "!free -m\n",
    "!echo\n",
    "!nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DASK Distributed Mortgage Workflow Runner\n",
    "\n",
    "Typically dask with cudf (dask-cudf) or dask with pandas dataframes is used to distribute the dataframe itself. Then operations are performed on the distributed dataframe. The implementation below differs. We will startup GPU dask workers (in my case I have 2 GPUs on the machine) and each worker will run the mortgage workflow to generate a DMatrix. Thus in the end what we will have can be thought of as a distributed DMatrix. It is just two DMatrices one on each worker. This distributed dmatrix is then passed to the dask-xgboost trainer.\n",
    "\n",
    "#### RECOMMEND TO RESTART THE JUPYTER KERNEL\n",
    "\n",
    "To release GPU resources from previous non-distributed runs above I recommend you RESTART the Jupyter kernel and continue with the cells below. Otherwise you might run out of memory and/or you might see additional processes consuming GPU.\n",
    "\n",
    "We start by clearing previous non-distributed run (in case the cells before this one were executed and Jupyter kernel was not restarted), and starting a dask cluster and client."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "env: NCCL_P2P_DISABLE=1\n",
      "\n",
      "HOST RAM\n",
      "              total        used        free      shared  buff/cache   available\n",
      "Mem:         128904        2140      108824        6325       17938      116973\n",
      "Swap:             0           0           0\n",
      "\n",
      "GPU STATUS\n",
      "pid, process_name, used_gpu_memory [MiB]\n",
      "8165, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 239 MiB\n",
      "\n",
      "\n",
      "\n",
      "DASK LOCAL CUDA CLUSTER\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<table style=\"border: 2px solid white;\">\n",
       "<tr>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3>Client</h3>\n",
       "<ul>\n",
       "  <li><b>Scheduler: </b>tcp://127.0.0.1:38964\n",
       "  <li><b>Dashboard: </b><a href='http://127.0.0.1:8787/status' target='_blank'>http://127.0.0.1:8787/status</a>\n",
       "</ul>\n",
       "</td>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3>Cluster</h3>\n",
       "<ul>\n",
       "  <li><b>Workers: </b>2</li>\n",
       "  <li><b>Cores: </b>8</li>\n",
       "  <li><b>Memory: </b>256.00 GB</li>\n",
       "</ul>\n",
       "</td>\n",
       "</tr>\n",
       "</table>"
      ],
      "text/plain": [
       "<Client: scheduler='tcp://127.0.0.1:38964' processes=2 cores=8>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Disable NCCL P2P. Only necessary for versions of NCCL < 2.4\n",
    "# https://rapidsai.github.io/projects/cudf/en/0.8.0/dask-xgb-10min.html#Disable-NCCL-P2P.-Only-necessary-for-versions-of-NCCL-%3C-2.4\n",
    "%env NCCL_P2P_DISABLE=1\n",
    "\n",
    "# CLEAN MEMORY FROM RUN BEFORE\n",
    "import gc\n",
    "\n",
    "from contextlib import suppress\n",
    "\n",
    "with suppress(Exception):\n",
    "    del(mortgage_feat_df_pandas)\n",
    "\n",
    "with suppress(Exception):\n",
    "    del(delinq_df_pandas)\n",
    "\n",
    "with suppress(Exception):\n",
    "    del(bst)\n",
    "\n",
    "gc.collect()\n",
    "\n",
    "from dask_cuda import LocalCUDACluster\n",
    "from dask.distributed import Client\n",
    "\n",
    "print('\\nHOST RAM')\n",
    "!free -m\n",
    "\n",
    "print('\\nGPU STATUS')\n",
    "# !nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n",
    "nvsmiquery = !nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n",
    "# Output will be empty if nothing is happening on GPUs.\n",
    "if len(nvsmiquery) == 1:\n",
    "    print('\\n'.join(nvsmiquery+['No running processes found']))\n",
    "else:\n",
    "    print('\\n'.join(nvsmiquery))\n",
    "\n",
    "print('\\n\\n')\n",
    "# Start cluster and dask client.\n",
    "\n",
    "memory_limit = 128e9\n",
    "threads_per_worker = 4\n",
    "cluster = LocalCUDACluster(\n",
    "    memory_limit=memory_limit,\n",
    "    threads_per_worker=threads_per_worker)\n",
    "client = Client(cluster)\n",
    "\n",
    "print('DASK LOCAL CUDA CLUSTER')\n",
    "client"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we define the gQuant workflow similar to how we did it before in the non-distributed case except we will use tasks `DaskMortgageWorkflowRunner` and `DaskXgbMortgageTrainer` in the `mortgage_gquant_plugins.py` module. Refer to these tasks in the `mortgage_gquant_plugins.py` for code details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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nmp6PT5+QNvve+/evYiOjgYAHDlyBNHR0dixY0en49FZLU8zjp3paBy//PJLWFlZcSeCVVdXIzY2FkZGRggJCQHw6P9h48aN8PT0RF1dHVavXg07OzsEBwdzC/ruDAN0Pg/05HsYCHx9fXH27Fls27YNn3zyCdasWdPxjYrb29bx2WefEY/Ho+PHj/feBhgdio+Pp/Hjx1NDQwMREclkMrKzs9Pa5hweHk4LFy7kugMDA2np0qVct7e3N129epWIiOrr62nixInca9u3b9faBvn+++/T7t27e1RjeXk5LVu2jABQTEwMpaamUk1NDY0fP548PT1p/fr1lJ2dTbW1tRQaGkqenp5a7//iiy8oNDSUmpubiYiooqKCvLy8aPLkyaRWqykhIYF8fX3JyMiItmzZQrt376bg4GAqLS2lWbNmkbu7u1Z7RUVFxOfz6f79+0REtHz5cgJAY8aMoQ0bNnRrmnWnjW3bttEPP/xARI/2Bfj5+ZGTkxPV1dV1e9otXryYTExMuPc0NzeTi4sLTZ06VWu4+fPn07179556enXn+66oqCAA9PHHH2v1z8zMbLPNuatannQcu9LZOE6fPp2cnZ21hh85ciRNmDCBiIgkEglNnTqVAND69espKyuL0tLSyNTUlBYtWtTtYYg6nwc6q3GgO336NBkbG1NsbGy7r7cJZ4lEQnw+n7Zv397nxfWHuro6EggEdODAAa3+c+fO1QrnKVOm0Keffsp1R0VFUUBAABE9+kcwMDCgf/3rX9zrx44d455r/lnFYjG9//77dOLEiSeq9auvviIAlJGRwfXbsmULAaC0tDSu3+bNmwkAlZeXExGRVColPp9PP/74o1Z733//PQGgffv2ERHRqlWrCADduXNHa7j2wnnjxo3k4uLCdefm5hIA2rVrF9evs2nWnTZKS0vJ0dFRa8fyhx9+SADo4MGDXU0uzsWLFwkAxcfHc/02bNhAJiYm9PDhQyIiamhooNdff52Inn56def77m44d7eWno5jd3U0jq+99lqbcJ4wYQIXzkREH3zwAQGgiooKrt/EiRPJy8ur28N0Zx7oqMZnwRdffEHm5uZUXFzc5rU2mzXOnj0LANiwYcPTrb/riStXrkAikWDkyJFa/R8/PvXixYv44IMP0NjYiO+++w43btzgrhdrbGyMiIgIbNiwATExMXj48CFee+21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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "from gquant.dataframe_flow import (TaskSpecSchema, TaskGraph)\n",
    "from mortgage_common import (\n",
    "    mortgage_etl_workflow_def, generate_mortgage_gquant_run_params_list,\n",
    "    MortgageTaskNames)\n",
    "\n",
    "\n",
    "# mortgage_data_path = '/datasets/rapids_data/mortgage'\n",
    "mortgage_data_path = './mortgage_data'\n",
    "\n",
    "# Using some default csv files for testing.\n",
    "gquant_task_spec_list = mortgage_etl_workflow_def()\n",
    "\n",
    "start_year = 2000\n",
    "end_year = 2001  # end_year is inclusive\n",
    "# end_year = 2016  # end_year is inclusive\n",
    "\n",
    "# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #\n",
    "# ADJUST PART_COUNT FOR YOUR SYSTEM MEMORY\n",
    "# able to do 18 with create_dmatrix_serially set to True\n",
    "# Otherwise need more host RAM. DGX-1 for Analytics has 1TB RAM.\n",
    "# part_count = 16  # the number of data files to train against\n",
    "# create_dmatrix_serially = False\n",
    "part_count = 18  # the number of data files to train against\n",
    "create_dmatrix_serially = True\n",
    "\n",
    "# Use RAPIDS Memory Manager. Seems to work fine without it.\n",
    "use_rmm = False\n",
    "\n",
    "# Clean up intermediate dataframes in the xgboost training task.\n",
    "delete_dataframes = True\n",
    "\n",
    "mortgage_run_params_dict_list = generate_mortgage_gquant_run_params_list(\n",
    "    mortgage_data_path, start_year, end_year, part_count, gquant_task_spec_list)\n",
    "\n",
    "_basedir = os.path.abspath('')  # path of current notebook\n",
    "mortgage_lib_module = os.path.join(_basedir, 'mortgage_gquant_plugins.py')\n",
    "\n",
    "# filter_dask_logger is primarily for displaying the log in the jupyter\n",
    "# notebook. The dask distributed logger is used by gQuant mortgage tasks\n",
    "# when running on dask workers.\n",
    "filter_dask_logger = True\n",
    "\n",
    "mortgage_workflow_runner_task = {\n",
    "    TaskSpecSchema.task_id:\n",
    "        MortgageTaskNames.dask_mortgage_workflow_runner_task_name,\n",
    "    TaskSpecSchema.node_type: 'DaskMortgageWorkflowRunner',\n",
    "    TaskSpecSchema.conf: {\n",
    "        'mortgage_run_params_dict_list': mortgage_run_params_dict_list,\n",
    "        'client': client,\n",
    "        'use_rmm': use_rmm,\n",
    "        'filter_dask_logger': filter_dask_logger\n",
    "    },\n",
    "    TaskSpecSchema.inputs: [],\n",
    "    TaskSpecSchema.filepath: mortgage_lib_module\n",
    "}\n",
    "\n",
    "# task_spec_list = [mortgage_workflow_runner_task]\n",
    "#\n",
    "# out_list = [MortgageTaskNames.dask_mortgage_workflow_runner_task_name]\n",
    "# task_graph = TaskGraph(task_spec_list)\n",
    "# ((mortgage_feat_df_delinq_df_pandas_futures),) = \\\n",
    "#     task_graph.run(out_list)\n",
    "#\n",
    "# print('MORTGAGE_FEAT_DF_DELINQ_DF_PANDAS_FUTURES: ',\n",
    "#       mortgage_feat_df_delinq_df_pandas_futures)\n",
    "\n",
    "dxgb_gpu_params = {\n",
    "    'nround':            100,\n",
    "    'max_depth':         8,\n",
    "    'max_leaves':        2 ** 8,\n",
    "    'alpha':             0.9,\n",
    "    'eta':               0.1,\n",
    "    'gamma':             0.1,\n",
    "    'learning_rate':     0.1,\n",
    "    'subsample':         1,\n",
    "    'reg_lambda':        1,\n",
    "    'scale_pos_weight':  2,\n",
    "    'min_child_weight':  30,\n",
    "    'tree_method':       'gpu_hist',\n",
    "    'n_gpus':            1,\n",
    "    'distributed_dask':  True,\n",
    "    'loss':              'ls',\n",
    "    # 'objective':         'gpu:reg:linear',\n",
    "    'objective':         'reg:squarederror',\n",
    "    'max_features':      'auto',\n",
    "    'criterion':         'friedman_mse',\n",
    "    'grow_policy':       'lossguide',\n",
    "    'verbose':           True\n",
    "}\n",
    "\n",
    "dxgb_trainer_task = {\n",
    "    TaskSpecSchema.task_id: MortgageTaskNames.dask_xgb_trainer_task_name,\n",
    "    TaskSpecSchema.node_type: 'DaskXgbMortgageTrainer',\n",
    "    TaskSpecSchema.conf: {\n",
    "        'create_dmatrix_serially': create_dmatrix_serially,\n",
    "        # Able to load 18 files with create_dmatrix_serially set\n",
    "        # to True. 16 is the max I could do otherwise.\n",
    "        'delete_dataframes': delete_dataframes,\n",
    "        'dxgb_gpu_params': dxgb_gpu_params,\n",
    "        'client': client,\n",
    "        'filter_dask_logger': filter_dask_logger\n",
    "    },\n",
    "    TaskSpecSchema.inputs: [\n",
    "        MortgageTaskNames.dask_mortgage_workflow_runner_task_name\n",
    "    ],\n",
    "    TaskSpecSchema.filepath: mortgage_lib_module\n",
    "}\n",
    "\n",
    "task_spec_list = [mortgage_workflow_runner_task, dxgb_trainer_task]\n",
    "\n",
    "task_graph = TaskGraph(task_spec_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Final step we run the workflow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "dask_mortgage_workflow_runner:INFO: TRYING TO LOAD 18 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: SPLIT MORTGAGE DATA INTO 2 CHUNKS AMONGST 2 WORKERS\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.186 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 RUNNING MORTGAGE gQUANT DataframeFlow\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.187 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 NCCL_P2P_DISABLE: 1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.187 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 CUDA_VISIBLE_DEVICES: 1,0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:26.555 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2000Q1.txt_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:33.357 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2000Q1.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:35.427 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 1 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:35.437 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2000Q2.txt_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:39.116 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2000Q2.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:40.510 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 2 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:40.519 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2000Q3.txt_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:44.204 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2000Q3.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:45.829 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 3 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:45.838 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2000Q4.txt_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:46.917 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2000Q4.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:47.546 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 4 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:47.555 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2000Q4.txt_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:51.655 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2000Q4.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:53.308 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 5 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:53.851 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2001Q1.txt_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:57.086 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2001Q1.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:58.362 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 6 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:58.482 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2001Q1.txt_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:02.956 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2001Q1.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:04.534 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 7 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:04.566 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_1_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:07.979 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:09.330 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 8 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:09.446 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_1_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:13.713 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 1 LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.459 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 LOADED 9 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.502 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 HOST RAM (MB) TOTAL 128904; USED 21174; FREE 89201\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.503 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 RUN PYTHON GARBAGE COLLECTION TO MAYBE CLEAR CPU AND GPU MEMORY\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.672 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 HOST RAM (MB) TOTAL 128904; USED 21169; FREE 89194\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.672 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 USING ARROW\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:15.672 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 ARROW TO PANDAS\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:17.698 distributed.worker.mortgage_workflow_runner:INFO: WORKER 1 HOST RAM (MB) TOTAL 128904; USED 33039; FREE 77243\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.186 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 RUNNING MORTGAGE gQUANT DataframeFlow\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.187 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 NCCL_P2P_DISABLE: 1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:24.187 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 CUDA_VISIBLE_DEVICES: 0,1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:26.550 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_0_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:32.901 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:34.969 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 1 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:34.980 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q2.txt_0_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:39.675 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q2.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:41.514 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 2 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:41.525 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q3.txt_1_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:44.327 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q3.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:45.528 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 3 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:45.538 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q3.txt_1_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:50.258 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q3.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:52.073 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 4 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:37:52.083 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q3.txt_0_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:10.848 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q3.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:12.093 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 5 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:12.388 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q3.txt_0_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:49.168 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q3.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:51.039 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 6 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:38:51.122 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q4.txt_1_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:39:34.755 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q4.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:39:42.022 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 7 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:39:42.076 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q4.txt_1_0\n",
      "dask_mortgage_workflow_runner:INFO: 14:40:26.012 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q4.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:40:27.790 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 8 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:40:27.933 distributed.worker.csv_mortgage_performance_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/perf/Performance_2001Q4.txt_0_1\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:07.497 distributed.worker.csv_mortgage_acquisition_data_loader:INFO: WORKER 0 LOADING: ./mortgage_data/acq/Acquisition_2001Q4.txt\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.174 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 LOADED 9 FRAMES\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.228 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 HOST RAM (MB) TOTAL 128904; USED 27832; FREE 78652\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.228 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 RUN PYTHON GARBAGE COLLECTION TO MAYBE CLEAR CPU AND GPU MEMORY\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.401 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 HOST RAM (MB) TOTAL 128904; USED 27832; FREE 78652\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.401 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 USING ARROW\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:09.402 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 ARROW TO PANDAS\n",
      "dask_mortgage_workflow_runner:INFO: 14:41:10.497 distributed.worker.mortgage_workflow_runner:INFO: WORKER 0 HOST RAM (MB) TOTAL 128904; USED 40387; FREE 66097\n",
      "dask_mortgage_workflow_runner:INFO: CLIENT INFO WHO HAS WHAT: {'mortgage_workflow_runner-524827d9eaa91df247185e42269277ca': ('tcp://10.31.229.79:38589',), 'mortgage_workflow_runner-a7b9d85ab42a72e0d3f7488f4b84b6af': ('tcp://10.31.229.79:36823',)}\n",
      "dask_xgb_trainer:INFO: CREATING DMATRIX SERIALLY ACROSS 2 WORKERS\n",
      "dask_xgb_trainer:INFO: 14:41:10.779 distributed.worker.make_xgb_dmatrix:INFO: CREATING DMATRIX ON WORKER 1\n",
      "dask_xgb_trainer:INFO: 14:42:25.666 distributed.worker.make_xgb_dmatrix:INFO: CREATING DMATRIX ON WORKER 0\n",
      "dask_xgb_trainer:INFO: JUST AFTER DMATRIX\n",
      "dask_xgb_trainer:INFO: HOST RAM (MB) TOTAL 128904; USED 77221; FREE 39151\n",
      "dask_xgb_trainer:INFO: RUNNING XGBOOST TRAINING USING DASK-XGBOOST\n",
      "XGBOOST BOOSTER:\n",
      " <xgboost.core.Booster object at 0x2b213c896b00>\n"
     ]
    }
   ],
   "source": [
    "# Look in the terminal where the jupyter was launched from\n",
    "# for real-time logging. Otherwise, the logging for individual\n",
    "# tasks is captured and displayed after the workers\n",
    "# complete that task.\n",
    "out_list = [MortgageTaskNames.dask_xgb_trainer_task_name]\n",
    "(bst,) = task_graph.run(out_list)\n",
    "\n",
    "print('XGBOOST BOOSTER:\\n', bst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "During Dask-XGBoost training on 2 GPUs above I observed:\n",
    "\n",
    "```\n",
    "$ nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv\n",
    "pid, process_name, used_gpu_memory [MiB]\n",
    "15945, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 9135 MiB\n",
    "15946, /home/avolkov/progs/python_installs/miniconda3/envs/py36-rapids/bin/python, 8681 MiB\n",
    "\n",
    "$ watch -n 0.5 nvidia-smi pmon -c 1\n",
    "# gpu        pid  type    sm   mem   enc   dec   command\n",
    "# Idx          #   C/G     %     %     %     %   name\n",
    "    0      15945     C    99    13     0     0   python\n",
    "    1      15946     C    99    12     0     0   python\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Summary\n",
    "\n",
    "We re-implemented the RAPIDS mortgage ETL notebook using gQuant. The benefit of the gQuant implementation is that one can readily break down their workflow into modular parts. It becomes much easier to understand and optimize the individual components of the workflow pipeline.\n",
    "\n",
    "A non-distributed and a distributed version was demonstrated. The benefits of distributed dask version were that more data was processed (18 files vs 12 files which amounts to 6GB of more data) and the dask-distributed version ran ETL in parallel on two workers (one worker per GPU) thus speeding up ETL. Using distributed version we could scale to multiple nodes as well.\n",
    "\n",
    "Two scripts are provided along with this notebook `mortgage_run_workflow_local.py` and `mortgage_run_workflow_daskdistrib.py` which run similar code to what was presented in this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "# CLEAN UP\n",
    "client.close()\n",
    "cluster.close()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
